﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using LGE;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Audio;

namespace UpUp
{
    public class Character : GameObject
    {
        Texture2D t = Engine.Load<Texture2D>("Images\\Tiles\\0");

        // atributos
        public double hp = 100;
        public float jumpHeight { get { return (float)(8 + 1.5 * attLevel[Att.Boot]); } }
        public float maxSpeedX { get { return (float)5 + attLevel[Att.Boot]; } }
        public int crystals = 0;
        public int nSessions = 1;
        public int maxH = 0;
        public int[] attCosts = new int[3] { 1, 4, 8 };
        public int[] attLevel = new int[4] { 0, 0, 0, 0 };

        // aux
        float gravityAcc = .4f;
        public bool isFacingLeft = false;
        public Vector2 speed = new Vector2(0, 0);
        public Vector2 normalDrawPosition;
        SpriteSheetState state;
        double timeToDie = 1050;
        bool onGround = false;
        bool jumping = false; // usada no pulo ajustável
        bool isJumpingWithKeyboard = false;

        // Sound
        const String SFX_Folder = "Audio\\SFX\\Char\\";
        SoundEffectInstance[] SFX_Steps = new SoundEffectInstance[4];
        String SFX_JumpFall = SFX_Folder + "JumpFall";
        String SFX_JumpJump = SFX_Folder + "JumpJump";
        String SFX_Die = SFX_Folder + "Die";
        public String SFX_Crystal = SFX_Folder + "Crystal";
        bool isDying = false;

        public Character(PlayScreen ps, Vector2 p)
            : base(ps, Engine.Load<Texture2D>("Images\\Character\\char"))
        {
            // State
            state = new SpriteSheetState(this);
            state.Add(Engine.Load<Texture2D>("Images\\Character\\idle"), new SpriteSheet(this, 12, 50, true));
            state.Add(Engine.Load<Texture2D>("Images\\Character\\walk"), new SpriteSheet(this, 20, 50, true));
            state.Add(Engine.Load<Texture2D>("Images\\Character\\jump"), new SpriteSheet(this, 8, 50, true));
            state.Add(Engine.Load<Texture2D>("Images\\Character\\fall"), new SpriteSheet(this, 6, 50, false));
            state.Add(Engine.Load<Texture2D>("Images\\Character\\death"), new SpriteSheet(this, 14, 75, false));
            state.Change(States.Idle);

            // Sound
            Audio.AddSFX(SFX_JumpFall);
            Audio.AddSFX(SFX_JumpJump);
            Audio.AddSFX(SFX_Die);
            Audio.AddSFX(SFX_Crystal);

            for (int i = 0; i < 4; i++)
            {
                SFX_Steps[i] = Engine.Load<SoundEffect>(SFX_Folder + "Steps" + i).CreateInstance();
                SFX_Steps[i].IsLooped = true;
            }

            prevPosition = p;
            position = p;
            normalDrawPosition = drawPosition = new Vector2(Engine.viewport.Width / 4 - 20, Engine.viewport.Height * 3 / 5);
        }

        public void Load()
        {
            if (ps.HMaxInThisSession > maxH) maxH = ps.HMaxInThisSession;
            timeToDie = 1050;
            alive = true;
            position = new Vector2(330, -30);
            speed = new Vector2(0, 0);
            hp = 100;
            isDying = false;
        }

        public override void Update(GameTime gameTime)
        {
            UpdateColdDamage(gameTime);

            if (hp <= 0)
            {
                if (!isDying)
                {
                    PauseSFXWalk(attLevel[Att.Boot]);
                    Audio.StopSong();
                    Audio.PlaySFX(SFX_Die);
                    state.Change(States.Death);
                    isDying = true;
                }
                hp = 0;
                timeToDie -= gameTime.ElapsedGameTime.TotalMilliseconds;
                if (timeToDie < 0) alive = false;
            }

            UpdatePosition(gameTime);

            if (!isDying && !onGround)
            {
                if (speed.Y < 0) state.Change(States.Jump);
                else state.Change(States.Fall);
            }

            state.Update(gameTime);
        }

        public void UpdateColdDamage(GameTime gameTime)
        {
            if (!ps.isLive) return;
            hp -= (float)(10 - (attLevel[Att.Coat] * 2.75)) * gameTime.ElapsedGameTime.TotalMilliseconds / 2000;
        }

        public void UpdatePosition(GameTime gameTime)
        {
            Vector2 oldPos = position;
            UpdateJump(gameTime);
            speed.X = UpdateSpeedX();
            if (!onGround) speed.Y += gravityAcc;
            position += speed;
            Vector2 newPos = position;

            // update Position.X
            position = new Vector2(newPos.X, oldPos.Y);
            if (speed.X != 0)
            {
                float dH = ps.bm.CollisionH(this, speed.X < 0);
                if (dH != 0)
                {
                    speed.X = 0;
                    position += new Vector2(dH, 0);
                }
            }

            // update Position.Y
            position = new Vector2(position.X, newPos.Y);
            if (!onGround)//speed.Y != 0)
            {
                float dV = ps.bm.CollisionV(this, speed.Y < 0);
                if (dV != 0)
                {
                    position += new Vector2(0, dV);
                    if (speed.Y > 0) // se estava caindo (não subindo)
                    {
                        Audio.PlaySFX(SFX_JumpFall);
                        onGround = true;
                    }
                    speed.Y = 0;
                }
            }
            else
            {
                position += new Vector2(0, 1);
                float dV = ps.bm.CollisionV(this, speed.Y < 0);
                if (dV != 0)
                    position += new Vector2(0, dV);
                else
                    onGround = false;
            }

            // não deixa o character cair para a esquerda do block1
            if (position.X - origin.X < ps.bm.block[0].position.X) position = new Vector2(ps.bm.block[0].position.X + origin.X, position.Y);

            // não deixa o character cair para a direita do block1, em baixo do block2
            if (position.Y + origin.Y > ps.bm.block[1].position.Y + Block.Height * Tile.Size &&
                position.X + origin.X > ps.bm.block[0].position.X + Block.Width * Tile.Size)
                position = new Vector2(ps.bm.block[0].position.X + Block.Width * Tile.Size - origin.X, position.Y);

            // não deixa o character cair do block1 e do block2
            // ainda n fiz =P

            // update DrawPosition
            if (position.X - normalDrawPosition.X < ps.bm.block[0].position.X)
                drawPosition.X = position.X - ps.bm.block[0].position.X;
            else drawPosition = normalDrawPosition;
        }

        public float UpdateSpeedX()
        {
            if (isDying) return 0;
            float speedx = speed.X;
            float inertiaIn = .2f + ((float)attLevel[Att.Spike] / 5);
            float inertiaOut = .1f + ((float)attLevel[Att.Spike] / 5);
            PauseSFXWalk(attLevel[Att.Boot]);
            if (ps.isLive && (Input.KeyDown(Keys.Right) || Input.ButtonDown(0, Buttons.LeftThumbstickRight)) && hp > 0)
            {
                speedx += inertiaIn;
                isFacingLeft = false;
                state.Change(States.Walk);
                if (speed.Y == 0) PlaySFXWalk(attLevel[Att.Boot]);
            }
            else if (ps.isLive && (Input.KeyDown(Keys.Left) || Input.ButtonDown(0, Buttons.LeftThumbstickLeft)) && hp > 0)
            {
                speedx -= inertiaIn;
                isFacingLeft = true;
                state.Change(States.Walk);
                if (speed.Y == 0) PlaySFXWalk(attLevel[Att.Boot]);
            }
            else if (speedx > 0)
            {
                state.Change(States.Idle);
                speedx -= inertiaOut;
                if (speedx < 0) speedx = 0;
            }
            else if (speedx < 0)
            {
                state.Change(States.Idle);
                speedx += inertiaOut;
                if (speedx > 0) speedx = 0;
            }
            else state.Change(States.Idle);

            if (speedx > maxSpeedX) speedx = maxSpeedX;
            else if (speedx < -maxSpeedX) speedx = -maxSpeedX;
            return speedx;
        }

        public void UpdateJump(GameTime gameTime)
        {
            if (speed.Y >= 0) jumping = false;
            if (speed.Y < -2 && jumping && (isJumpingWithKeyboard ? Input.KeyUp(Keys.Space) : Input.ButtonUp(0, Buttons.A))) { speed.Y = -2; jumping = false; }
            if (ps.isLive && onGround && (Input.KeyPressed(Keys.Space) || Input.ButtonPressed(Buttons.A)) && hp > 0)
            {
                foreach (SoundEffectInstance s in SFX_Steps) s.Stop();
                Audio.PlaySFX(SFX_JumpJump);
                isJumpingWithKeyboard = Input.KeyPressed(Keys.Space);
                jumping = true;
                onGround = false;
                speed.Y -= jumpHeight;
            }
        }

        public override void Draw(SpriteBatch spriteBatch)
        {
            if (alive)
            {
                spriteBatch.Draw(sprite, drawPosition, sourceRectangle, Color.White, rotation, origin, scale, isFacingLeft ? SpriteEffects.FlipHorizontally : SpriteEffects.None, 0);
                //DrawCollisionBox(spriteBatch);
            }
        }

        public void DrawCollisionBox(SpriteBatch spriteBatch)
        {
            ps.bm.block[0].GetCollidingTiles(this).ForEach(p => spriteBatch.Draw(t, ps.bm.block[0].position + (Tile.Size * new Vector2(p.X, p.Y)) - this.position + this.drawPosition, Color.White));
            ps.bm.block[1].GetCollidingTiles(this).ForEach(p => spriteBatch.Draw(t, ps.bm.block[1].position + (Tile.Size * new Vector2(p.X, p.Y)) - this.position + this.drawPosition, Color.White));
        }

        private void PlaySFXWalk(int bootLevel)
        {
            bootLevel = (int)MathHelper.Clamp(bootLevel, 0, 3);
            SFX_Steps[bootLevel].Play();
        }

        private void PauseSFXWalk(int bootLevel)
        {
            bootLevel = (int)MathHelper.Clamp(bootLevel, 0, 3);
            SFX_Steps[bootLevel].Pause();
        }

        public bool AddAtt(int att)
        {
            if (attLevel[att] >= 3) return false;
            int cost = attLevel[att] < 0 ? 0 : attCosts[attLevel[att]];
            if (crystals < cost) return false;

            crystals -= cost;
            attLevel[att]++;
            attLevel[att] = (int)MathHelper.Clamp(attLevel[att], 0, 3);
            return true;
        }
    }

    public static class Att
    {
        public static int Visor = 0;
        public static int Coat = 1;
        public static int Boot = 2;
        public static int Spike = 3;
    }

    public static class States
    {
        public static int Idle = 0;
        public static int Walk = 1;
        public static int Jump = 2;
        public static int Fall = 3;
        public static int Death = 4;
    }
}
